<?php
namespace models\common;

defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * BinaryBit-Based Status Trait
 * 
 * @desc    基于二进制位的状态位, 提供相关的功能扩展
 * @version 1.0.0 
 * @author  Jason
 * @date	2017年5月11日 下午5:53:01
 * 
 * @version 1.1.0
 * @date	2017年5月17日 下午1:35:18
 * @desc	比特位要从右起数，便于平滑添加比特位！
 * 			最容易想到的放最右边，但是注释可以按人类习惯来。于是，状态说明和状态位恰好逆序！
 * 
 * @version 1.2.0
 * @date	2017年6月14日 下午1:06:40
 * @desc	可以说本特性（Trait）是依赖数据库的，那么就需要支持的表映射！
 * 			另外，让 config 默认值更具代表性和通用性。
 * 
 * @version 1.3.0
 * @date	2017年7月25日 下午3:18:36
 * @desc	修复本特性（Trait）中有些函数 $mapping 参数传递中断的 🐞BUG👹 ！
 */
trait BinaryBitBasedStatus
{
	// 状态位【从右向左】依次为是否启用、是否激活、是否审核、是否无限制, 下面写法反着来！
	#protected $status_bit_length = 4;
	#protected $status_bit_positive_tags = ['unlimited', 'checked', 'activated', 'enabled'];
	#protected $status_bit_negative_tags = ['limited', 'unchecked', 'unactivated', 'disabled'];
	// MARK 特性（Trait）：支持的表映射的配置信息
	// 本次更新之所以依然留着该字段是考虑要兼顾到不用 tags 的情况。
	// 另外，让 config 默认值更具代表性和通用性。
	protected $status_bit_config = [
			'master' => [
					'status_bit_length' => 1,
					'status_bit_positive_tags' => ['enabled'],
					'status_bit_negative_tags' => ['disabled'],
			],
			'slaves' => [],
	];
	
	/**
	 * 标记状态位
	 *
	 * @desc    标记状态位，去0写1
	 *
	 * @author  Jason
	 * @date    2017年5月9日 下午5:19:26
	 * 
	 * @date	2017年5月11日 下午5:55:00
	 * @desc	该方法摘自Mod_member! 下同
	 *
	 * @param number $id		用户ID
	 * @param number $rightBit	右起比特位，从"1"开始（状态位【从右向左】依次为是否启用、是否激活、是否审核、是否无限制）
	 * @param string $mapping	表映射
	 * 
	 * @version 1.1.0
	 * @date	2017年5月17日 下午1:35:18
	 * @desc	比特位要从右起数，便于平滑添加比特位！
	 * @desc	操作总是会成功，但不一定总是会起作用！
	 *
	 * @//version	1.0.0
	 * @//return	null
	 * @//return	void 静默成功
	 * 
	 * @version	1.2.0
	 * @date	2017年6月20日 上午11:04:33
	 * @desc	虽然操作总是会成功，但返回值会告诉调用者是否会起作用！
	 * 
	 * @return	boolean 如果受影响的行数大于0，返回真
	 */
	public function mark_status($id, $rightBit = 1, $mapping = 'master')
	{
		$status_bit_length = $this->status_bit_config[$mapping]['status_bit_length'];
		$binary = str_repeat('0', $status_bit_length); // '0000';
		$leftBitIndex = $status_bit_length - $rightBit;
		$binary{$leftBitIndex} = '1';
		$mask = bindec($binary);
		$this->db->set('status', "status|$mask", false); // logins = logins+1
		$this->db->where('id', $id);
		$os = $this->db->update($this->table_mapper[$mapping]);
		// MARK 如果这里硬要看这次操行执行的成果，这里返回值不能为 oper_status，因为 oper_status == true 未必 affected_rows > 0，得看受影响的行数！
		return ($this->db->affected_rows() > 0);
	}
	
	/**
	 * 取消标记状态位
	 *
	 * @desc    取消标记状态位，去1写0
	 *
	 * @author  Jason
	 * @date    2017年5月9日 下午5:32:33
	 *
	 * @param number $id		用户ID
	 * @param number $rightBit	右起比特位，从"1"开始
	 * @param string $mapping	表映射
	 * 
	 * @//version	1.0.0
	 * @//return	null
	 * @//return	void 静默成功
	 * 
	 * @version	1.2.0
	 * @date	2017年6月20日 上午11:04:33
	 * @desc	虽然操作总是会成功，但返回值会告诉调用者是否会起作用！
	 * 
	 * @return	boolean 如果受影响的行数大于0，返回真
	 */
	public function unmark_status($id, $rightBit = 1, $mapping = 'master')
	{
		$status_bit_length = $this->status_bit_config[$mapping]['status_bit_length'];
		$binary = str_repeat('1', $status_bit_length); // '1111'
		$leftBitIndex = $status_bit_length - $rightBit;
		$binary{$leftBitIndex} = '0';
		$mask = bindec($binary);
		$this->db->set('status', "status&$mask", false); // logins = logins+1
		$this->db->where('id', $id);
		$this->db->update($this->table_mapper[$mapping]);
		
		return ($this->db->affected_rows() > 0);
	}
	
	/**
	 * 切换状态启用禁用（原路线上下架）
	 *
	 * @author  Jason
	 * @date    2017年5月24日 上午10:26:52
	 *
	 * @param number $id
	 * @param string $rightBit	右起比特位，从"1"开始
	 * @param string $mapping	表映射
	 *
	 * @return string 最终的状态标签 $ultimateTag
	 */
	public function toggle_status($id, $rightBit = 1, $mapping = 'master')
	{
		// 获取数据库中保存的状态位数字
		$status = $this->select_status($id, $mapping);
		
		// 计算正负Tag
		$status_bit_length = $this->status_bit_config[$mapping]['status_bit_length'];
		$leftBitIndex = $status_bit_length - $rightBit;
		$positiveTag = $this->status_bit_config[$mapping]['status_bit_positive_tags'][$leftBitIndex];
		$negativeTag = $this->status_bit_config[$mapping]['status_bit_negative_tags'][$leftBitIndex];
		
		// 切换并计算最终值
		$tags = $this->calculate_status_tags($status, 'positive', $mapping);
		if (in_array($positiveTag/*'enabled'*/, $tags)) {
			// 去除启用标记
			$this->unmark_status($id, $rightBit, $mapping);
			$ultimateTag = $negativeTag/*'disabled'*/;
		} else {
			// 添加启用标记
			$this->mark_status($id, $rightBit, $mapping);
			$ultimateTag = $positiveTag/*'enabled'*/;
		}
		
		return $ultimateTag;
	}
	
	/**
	 * 查询状态位
	 * 
	 * @desc    直接从数据库中查询状态位
	 * @author  Jason
	 * @date    2017年6月14日 下午1:31:16
	 * 
	 * @param   number $id		用户ID
	 * @param   string $mapping	表映射
	 * 
	 * @return  number	数据库中保存的状态位数字
	 * 	        eg:	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ...
	 */
	public function select_status($id, $mapping = 'master')
	{
		$this->db->from($this->table_mapper[$mapping]);
		$this->db->select('status');
		$this->db->where('id', $id);
		return $this->db->get()->row_array()['status'];
	}
	
	/**
	 * 把 十进制数字状态位 计算转化为 字符串形式的二进制数字
	 * 
	 * @param number $status
	 * @param string $mapping	表映射
	 * 
	 * @return string
	 */
	public function calculate_status_binary($status, $mapping = 'master')
	{
		$status_bit_length = $this->status_bit_config[$mapping]['status_bit_length'];
		$binary = decbin($status);
		$binary = str_pad($binary, $status_bit_length, '0', STR_PAD_LEFT); // 前面补"0"!
		// or sprintf("%04d", $binary);
		return $binary;
	}
	
	/**
	 * 把 十进制数字状态位 计算转化为 状态字符串标签数组
	 * 
	 * @param number $status
	 * @param string $attitude	positive/negative !negative => positive
	 * @param string $mapping	表映射
	 * 
	 * @return array 状态位代表的状态标签数组
	 * 			eg: ['activated', 'enabled'] or ['limited', 'unchecked']
	 */
	public function calculate_status_tags($status, $attitude = 'positive', $mapping = 'master')
	{
		$attitude = in_array($attitude, ['positive', 'negative']) ? $attitude : 'positive';
		// $list = $this->${"status_bit_{$attitude}_tags"};
		// $list = $this->{"status_bit_{$attitude}_tags"};
		$list = $this->status_bit_config[$mapping]["status_bit_{$attitude}_tags"];
		
		$tags = [];
		$binary = $this->calculate_status_binary($status, $mapping);
		$bit = ($attitude === 'positive') ? '1' : '0';
		for ($i = 0; $i < strlen($binary)/*count($binary)*/; $i += 1) {
			if ($binary{$i} === $bit) {
				if (isset($list[$i])) {
					$tags[] = $list[$i];
				} else {
					// 脏数据导致下标越界 错误日志
					log_message('error', sprintf("The Index '%s' which of the Status_bit_positive/negative_tags '%s' is OUT of RANGE!", $i, var_export($list, true)));
				}
			}
		}
		return $tags;
	}
	
	/**
	 * 把 状态字符串标签数组 计算转化为 以`status`为参照的正向查询条件
	 * 
	 * @desc    可以直接把返回的条件返回输入 get_by_conditions
	 * 			仅正向标签，（即判断相应二进制位为"1"）
	 * @author  Jason
	 * @date    2017年5月17日 下午2:12:21
	 * 
	 * @version 1.1.0
	 * @desc	1.0.0 仅支持正向标签，现在加上负向标签的支持！
	 * @date	2017年6月15日 下午6:38:18
	 * 
	 * @//param   array  $tags 		状态字符串标签数组, //仅正向标签 eg: ['enabled'] or ['activated', 'enabled']
	 * @param   array  $tags 		状态字符串标签数组, 支持正向标签和负向标签，注意要与传递的第二个参数匹配！
	 * 								eg:
	 * 									当 $attitude != 'negative' 时： ['enabled'] or ['activated', 'enabled']
	 * 									当 $attitude == 'negative' 时： ['disabled'] or ['unactivated', 'disabled']
	 * @param   string $attitude	positive/negative !negative => positive
	 * @param   string $mapping		表映射
	 * 
	 * @return  array 一条查询条件
	 * 	        eg:	["`status` & 8 > 0", null, false]
	 */
	public function calculate_status_condition_where($tags = ['enabled'], $attitude = 'positive', $mapping = 'master')
	{
		$status_bit_length = $this->status_bit_config[$mapping]['status_bit_length'];
		$status_bit_positive_tags = $this->status_bit_config[$mapping]['status_bit_positive_tags'];
		
		# // 仅正向标签时的逻辑
		#$binary = str_repeat('0', $status_bit_length); // '0000';
		#foreach ($status_bit_positive_tags as $leftBitIndex => $tag) {
		#	if (in_array($tag, $tags)) {
		#		$binary{$leftBitIndex} = '1';
		#	}
		#}
		#
		#$mask = bindec($binary);
		#$where = ["`status` & $mask = $mask", null, false];
		
		// 全向（正负向）标签的逻辑
		$attitude = in_array($attitude, ['positive', 'negative']) ? $attitude : 'positive';
		if ($attitude === 'positive') {
			// 正向：1 & 1 => 1，其余位用 0 做掩码，assert(* & 0 == 0) => 盖掉不判断，忽略！
			$initBit = 0;
			$maskBit = 1;
			$bitSign = '&';
		} else {
			// 负向：0 | 0 => 0，其余位用 1 做掩码，assert(* | 1 == 1) => 盖掉不判断，忽略！
			$initBit = 1;
			$maskBit = 0;
			$bitSign = '|';
		}
		
		$binary = str_repeat($initBit, $status_bit_length); // 正向：'0000'; 负向：'1111';
		foreach ($status_bit_positive_tags as $leftBitIndex => $tag) {
			if (in_array($tag, $tags)) {
				$binary{$leftBitIndex} = $maskBit; // 正向：'1'; 负向：'0';
			}
		}
		
		$mask = bindec($binary);
		$where = ["`status` $bitSign $mask = $mask", null, false]; // 正向：'&'; 负向：'|';
		
		return $where;
	}
	
	/**
	 * 把 状态字符串标签数组 计算转化为 字符串形式的二进制数字
	 *
	 * @param array  $tags		eg: ['activated', 'enabled'] or ['limited', 'unchecked']
	 * @param string $attitude	positive/negative !negative => positive
	 * @param string $mapping	表映射
	 * 
	 * @author	Jason
	 * @desc	$attitude 参数只要不是 negative 就是 positive, 所以如果想要传 negative, 务必拼写正确！
	 * @date	2017年6月15日 下午6:25:11
	 * 
	 * @return string 字符串形式的二进制数字
	 * 			eg: 0000 or 0001 or ... or 1111
	 */
	public function calculate_binary_by_tags($tags, $attitude = 'positive', $mapping = 'master')
	{
		$attitude = in_array($attitude, ['positive', 'negative']) ? $attitude : 'positive';
		$list = $this->status_bit_config[$mapping]["status_bit_{$attitude}_tags"];
		
		$binary = '';
		$attitudeBit = ($attitude === 'positive') ? '1' : '0';
		$reverseBit = ($attitude !== 'positive') ? '1' : '0';
		foreach ($list as $tag) {
			$binary .= in_array($tag, $tags) ? $attitudeBit : $reverseBit;
		}
		
		$status_bit_length = $this->status_bit_config[$mapping]['status_bit_length'];
		if (strlen($binary) != $status_bit_length) {
			// 错误配置状态长度不匹配 错误日志
			log_message('error', sprintf("The Length of the BinaryBit '%d' is NOT MATCH to the size of (count) %d Status_bit_positive/negative_tags '%s'!", strlen($binary), $status_bit_length, var_export($list, true)));
		}
		
		return $binary;
	}
	/**
	 * 把 字符串形式的二进制数字 计算转化为 十进制数字状态位
	 *
	 * @param string $binary	eg: 0000 or 0001 or ... or 1111
	 * 
	 * //@param string $mapping	表映射
	 * @desc	另外，此函数不需要表映射！
	 * @date	2017年7月25日 下午3:25:16
	 *
	 * @return number
	 */
	public function calculate_status_by_binary($binary)
	{
		$decimal = bindec($binary);
		return $decimal;
	}
	
	/**
	 * 把 状态字符串标签数组 计算转化为 十进制数字状态位 - 应用场景：初始化状态，重置指定状态
	 *
	 * @param array  $tags		eg: ['activated', 'enabled'] or ['limited', 'unchecked']
	 * @param string $attitude	positive/negative !negative => positive
	 * @param string $mapping	表映射
	 *
	 * @return number
	 *
	 */
	public function calculate_status_by_tags($tags, $attitude = 'positive', $mapping = 'master')
	{
		$binary = $this->calculate_binary_by_tags($tags, $attitude, $mapping);
		$status = $this->calculate_status_by_binary($binary);
		return $status;
	}
	
	/**
	 * 计算右起比特位
	 * 
	 * @author  Jason
	 * @date	2017年6月20日 上午10:38:54
	 * @desc	抽取自 toggle_status_by_tag：既然这一段在 mark_status_by_tag 中又被使用，说明它是有复用价值的，抽出之。
	 *
	 * @param number $id
	 * @param string $positiveTag	正向Tag代表 -> 右起比特位
	 * @param string $mapping		表映射
	 *
	 * @return string 最终的状态标签 $ultimateTag
	 */
	public function calculate_rightBit_by_tag($id, $positiveTag = 'enabled', $mapping = 'master')
	{
		// 计算右起比特位
		$statusBitLength = $this->status_bit_config[$mapping]['status_bit_length'];
		$positiveTags = $this->status_bit_config[$mapping]['status_bit_positive_tags'];
		$leftBitIndex = array_search($positiveTag, $positiveTags);
		$rightBit = $statusBitLength - $leftBitIndex;
		
		return $rightBit;
	}
	
	
	// MARK ------------------以下是模型偏重业务的方法------------------
	// 12+18+32+18
	/**
	 * 切换状态启用禁用（原路线上下架）
	 *
	 * @author  Jason
	 * @date    2017年5月24日 上午10:26:52
	 *
	 * @param number $id
	 * @param string $positiveTag	正向Tag代表 -> 右起比特位
	 * @param string $mapping		表映射
	 * 
	 * @return string 最终的状态标签 $ultimateTag
	 */
	public function toggle_status_by_tag($id, $positiveTag = 'enabled', $mapping = 'master')
	{
		// 计算右起比特位
		$rightBit = $this->calculate_rightBit_by_tag($id, $positiveTag, $mapping);
		
		return $this->toggle_status($id, $rightBit, $mapping);
	}
	
	/**
	 * 根据状态标签标记状态位
	 * 
	 * @desc	阻断（关联）状态位使用
	 * @author  Jason
	 * @date	2017年6月20日 上午10:26:19
	 *
	 * @param number $id
	 * @param string $positiveTag	正向Tag代表 -> 右起比特位
	 * @param string $mapping		表映射
	 *
	 * @return null
	 */
	public function mark_status_by_tag($id, $positiveTag = 'checked', $mapping = 'master')
	{
		// 计算右起比特位
		$rightBit = $this->calculate_rightBit_by_tag($id, $positiveTag, $mapping);
		
		return $this->mark_status($id, $rightBit, $mapping);
	}
	
	/**
	 * 根据状态标签取消标记状态位
	 *
	 * @desc	阻断（关联）状态位使用
	 * @author  Jason
	 * @date	2017年6月20日 上午10:26:19
	 *
	 * @param number $id
	 * @param string $positiveTag	正向Tag代表 -> 右起比特位
	 * @param string $mapping		表映射
	 *
	 * @return null
	 */
	public function unmark_status_by_tag($id, $positiveTag = 'checked', $mapping = 'master')
	{
		// 计算右起比特位
		$rightBit = $this->calculate_rightBit_by_tag($id, $positiveTag, $mapping);
		
		return $this->unmark_status($id, $rightBit, $mapping);
	}
}
